package com.sikulifang.bank.simulation.server.handler;

import com.sikulifang.bank.simulation.persistence.Bank;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;

import java.nio.ByteOrder;
import java.util.List;

/**
 * 统一端口的处理器
 * 使用同一个端口去处理TCP/HTTP协议的请求，因为HTTP的底层协议也是TCP，
 * 因此可以在此处理器内部可以通过解析部分数据，来判断请求是TCP请求还是HTTP请求，
 * 然使用动态的pipeline切换
 *
 * @author zhoul
 * @since 2.0.0
 */
@Data
@Slf4j
@EqualsAndHashCode(callSuper = true)
public class PortUnificationServerHandler extends ByteToMessageDecoder {

    private static final int SOCKET_BYTE_HEADER_LENGTH = 4;

    private static final String[] SSL_CONTEXT_PROTOCOLS = new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"};

    private ApplicationContext applicationContext;

    private Bank bank;

    @Override
    @SneakyThrows
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) {
        //判断初始化代理类型
        if (Bank.HTTP.equals(bank.getType())) {
            switchToHttp(channelHandlerContext);
        } else {
            LengthFieldBasedFrameDecoder lengthFieldBasedFrameDecoder = new LengthFieldBasedFrameDecoder(
                    //小端模式
                    ByteOrder.LITTLE_ENDIAN,
                    //消息允许最大长度
                    104857600,
                    //消息长度在消息体里面的偏移量
                    5,
                    //消息长度字段占用长度
                    8,
                    //
                    0,
                    //跳过不是消息部分的长度（整个消息体去掉消息头的协议和消息长度）
                    13,
                    true);

            SocketHandler socketHandler = new SocketHandler(applicationContext);
            socketHandler.setBank(bank);

            ChannelPipeline pipeline = channelHandlerContext.pipeline();
            pipeline
                    // .addLast(lengthFieldBasedFrameDecoder)
                    .addLast(socketHandler)
                    .remove(this);
        }
    }

    /**
     * 跳转到http处理
     *
     * @param ctx 上下文
     */
    private void switchToHttp(ChannelHandlerContext ctx) {

        ChannelPipeline pipeline = ctx.pipeline();

        // http请求解码
        pipeline.addLast(new HttpRequestDecoder());
        // http响应加密
        pipeline.addLast(new HttpResponseEncoder());
        // 添加了该项后，在handler中才能使用fullRequest
        pipeline.addLast(new HttpObjectAggregator(1024 * 1024));
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new HttpContentCompressor());

        HttpHandler proxyConnectHandler = new HttpHandler();
        proxyConnectHandler.setBank(bank);
        proxyConnectHandler.setApplicationContext(applicationContext);
        pipeline.addLast(proxyConnectHandler);
        pipeline.remove(this);
    }

    /**
     * 判断请求是否是HTTP请求
     *
     * @param magic1 报文第一个字节
     * @param magic2 报文第二个字节
     * @return 是否为HTTP请求
     */
    private boolean isHttp(int magic1, int magic2) {
        // GET
        return magic1 == 'G' && magic2 == 'E'
               // POST
               || magic1 == 'P' && magic2 == 'O'
               // PUT
               || magic1 == 'P' && magic2 == 'U'
               // HEAD
               || magic1 == 'H' && magic2 == 'E'
               // OPTIONS
               || magic1 == 'O' && magic2 == 'P'
               // PATCH
               || magic1 == 'P' && magic2 == 'A'
               // DELETE
               || magic1 == 'D' && magic2 == 'E'
               // TRACE
               || magic1 == 'T' && magic2 == 'R'
               // CONNECT
               || magic1 == 'C' && magic2 == 'O';
    }

}
